#定义一个简单的神经网络，然后求梯度
import sys, os
sys.path.append(os.pardir)
import numpy as np

#定义softmax函数
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T
    #溢出对策
    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))

#定义损失函数：交叉熵误差
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    #监督数据是one-hot-vertor的情况下，转换为正确解标签的索引
    if t.size == y.size:
        t = t.argmax(axis=1)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size    

#定义梯度函数
def numerical_gradient(f, x):
    h = 1e-4  #0.0001
    grad = np.zeros_like(x)
    #初始化迭代器
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    #使用迭代器
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)  #相当于f(x+h)

        x[idx] = tmp_val - h
        fxh2 = f(x)  #相当于f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        #还原值
        x[idx] = tmp_val
        it.iternext()
    return grad

#定义一个名为SimpleNet的类，参数x接收输入数据，参数t接收正确解标签
class SimpleNet:
    def __init__(self):
        #用高斯分布进行初始化实例变量，即形状为2×3的权重参数
        #self.W = np.random.randn(2, 3)
        self.W = np.array([[0.47355232, 0.9977393, 0.84668094],[0.85557411, 0.03563661, 0.69422093]])

    #定义用于预测的方法
    def predict(self, x):
        return np.dot(x, self.W)

    #定义用于求损失函数的方法
    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)
        return loss

#实例化一个类
net = SimpleNet()
#屏幕输出权重参数
print(net.W)
x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)
#最大的索引
maxIndex = np.argmax(p)
print(maxIndex)
#正确的标签
t = np.array([0, 0, 1])
loss = net.loss(x,t)
print('loss:' + str(loss))
#接下来求梯度：使用numerical_gradient(f, x)函数
#定义函数f(w)，参数w是一个伪参数，因为numerical_gradient(f, x)会在内部执行f(x)，为了与之兼容而定义了f(w)
def f(W):
    return net.loss(x, t)

#求梯度
dW = numerical_gradient(f, net.W)
print(dW)
